#include "draw.h"
#include "state.h"
#include "config.h"
#include <X11/extensions/Xrender.h>
#include <cstdio>
#include <cstring>

// Fnt destructor (auto-clean Xft resources)
Fnt::~Fnt()
{
    if (pattern)
        FcPatternDestroy(pattern);
    if (xfont && dpy)
        XftFontClose(dpy, xfont);
}

// Drw constructor (initialize X11 drawing resources)
Drw::Drw(Display *d, int s, Window r, unsigned int w_, unsigned int h_)
    : dpy(d), screen(s), root(r), w(w_), h(h_)
{
    // Create offscreen pixmap (double-buffering to avoid flicker)
    drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
    // Create graphics context with default settings
    gc = XCreateGC(dpy, root, 0, nullptr);
    // Set line attributes (solid, 1px width)
    XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
}

// Drw destructor (clean X11 resources)
Drw::~Drw()
{
    if (drawable)
        XFreePixmap(dpy, drawable);
    if (gc)
        XFreeGC(dpy, gc);
}

// Resize drawable (e.g., when window is resized)
void Drw::resize(unsigned int w_, unsigned int h_)
{
    if (w == w_ && h == h_)
        return;
    w = w_;
    h = h_;
    // Recreate pixmap with new dimensions
    XFreePixmap(dpy, drawable);
    drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
}

// Load fonts from config (returns true if at least one font loads)
bool Drw::loadFonts(const std::vector<std::string> &fontNames)
{
    fonts.clear();
    for (const auto &name : fontNames)
    {
        auto fnt = std::make_unique<Fnt>();
        fnt->dpy = dpy;
        // Open font with Xft (supports fontconfig patterns)
        fnt->xfont = XftFontOpenName(dpy, screen, name.c_str());
        if (!fnt->xfont)
            continue;
        // Create font pattern for future use
        fnt->pattern = FcNameParse((FcChar8 *)name.c_str());
        // Calculate font height (ascent + descent)
        fnt->h = fnt->xfont->ascent + fnt->xfont->descent;
        fonts.push_back(std::move(fnt));
    }
    return !fonts.empty();
}

// Allocate Xft colors from config (caches in Drw::scheme)
void Drw::allocColors(const Config &cfg)
{
    Visual *vis = DefaultVisual(dpy, screen);
    Colormap cmap = DefaultColormap(dpy, screen);
    // Allocate colors for each scheme (NORM/SEL/OUT) and type (FG/BG)
    for (size_t s = 0; s < (size_t)Scheme::LAST; ++s)
    {
        for (int c = 0; c < 2; ++c)
        {
            const Color &cfgColor = cfg.colors[s][c];
            XRenderColor renderColor;
            // Convert 8-bit (0-255) to 16-bit (0-65535) for XRender
            renderColor.red = static_cast<unsigned short>(cfgColor.r * 257);
            renderColor.green = static_cast<unsigned short>(cfgColor.g * 257);
            renderColor.blue = static_cast<unsigned short>(cfgColor.b * 257);
            renderColor.alpha = static_cast<unsigned short>(cfgColor.a * 257);
            // Allocate color and store in scheme cache
            XftColorAllocValue(dpy, vis, cmap, &renderColor, &scheme[s][c]);
        }
    }
}

// Calculate width of text (uses first loaded font)
unsigned int Drw::getTextWidth(const char *text)
{
    if (!text || fonts.empty() || !fonts[0]->xfont)
        return 0;
    XGlyphInfo ext;
    // Get text extents with Xft (supports Unicode)
    XftTextExtentsUtf8(dpy, fonts[0]->xfont, (XftChar8 *)text, strlen(text), &ext);
    return ext.xOff;
}

// Draw a single menu item (background + text)
void Drw::drawItem(Item *item, int x, int y, int w)
{
    if (!item || fonts.empty() || !fonts[0]->xfont)
        return;
    State &state = getState();
    XftDraw *xftDraw = XftDrawCreate(dpy, drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));

    // Select color scheme (selected/normal/output)
    XftColor *fg = &scheme[(size_t)Scheme::NORM][0];
    XftColor *bg = &scheme[(size_t)Scheme::NORM][1];
    if (item == state.sel)
    {
        fg = &scheme[(size_t)Scheme::SEL][0];
        bg = &scheme[(size_t)Scheme::SEL][1];
    }
    else if (item->out)
    {
        fg = &scheme[(size_t)Scheme::OUT][0];
        bg = &scheme[(size_t)Scheme::OUT][1];
    }

    // Draw background (filled rectangle)
    XSetForeground(dpy, gc, bg->pixel);
    XFillRectangle(dpy, drawable, gc, x, y, w, state.bh);

    // Draw text (centered vertically)
    int textY = y + (state.bh - fonts[0]->h) / 2 + fonts[0]->xfont->ascent;
    XftDrawStringUtf8(xftDraw, fg, fonts[0]->xfont, x + state.lrpad / 2, textY, (XftChar8 *)item->text.c_str(), item->text.size());

    XftDrawDestroy(xftDraw);
}

// Draw entire menu (background + prompt + input + items)
void Drw::drawMenu()
{
    State &state = getState();
    if (!state.win)
        return;

    // Clear background with normal background color
    XSetForeground(dpy, gc, scheme[(size_t)Scheme::NORM][1].pixel);
    XFillRectangle(dpy, drawable, gc, 0, 0, w, h);

    int x = 0, y = 0;
    XftDraw *xftDraw = XftDrawCreate(dpy, drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));

    // Draw prompt (if enabled)
    if (!state.cfg.prompt.empty())
    {
        XftColor *fg = &scheme[(size_t)Scheme::SEL][0];
        XftColor *bg = &scheme[(size_t)Scheme::SEL][1];
        // Prompt background
        XSetForeground(dpy, gc, bg->pixel);
        XFillRectangle(dpy, drawable, gc, x, y, state.promptw, state.bh);
        // Prompt text
        int textY = y + (state.bh - fonts[0]->h) / 2 + fonts[0]->xfont->ascent;
        XftDrawStringUtf8(xftDraw, fg, fonts[0]->xfont, x + state.lrpad / 2, textY, (XftChar8 *)state.cfg.prompt.c_str(), state.cfg.prompt.size());
        x += state.promptw;
    }

    // Draw input field (user text)
    XSetForeground(dpy, gc, scheme[(size_t)Scheme::NORM][1].pixel);
    XFillRectangle(dpy, drawable, gc, x, y, state.inputw, state.bh);
    XftColor *inputFg = &scheme[(size_t)Scheme::NORM][0];
    int textY = y + (state.bh - fonts[0]->h) / 2 + fonts[0]->xfont->ascent;
    XftDrawStringUtf8(xftDraw, inputFg, fonts[0]->xfont, x + state.lrpad / 2, textY, (XftChar8 *)state.text.c_str(), state.text.size());
    x += state.inputw;

    // Draw items (visible page)
    y += state.bh;
    Item *currItem = state.curr;
    while (currItem && y < h)
    {
        drawItem(currItem, 0, y, w);
        currItem = currItem->right;
        y += state.bh;
    }

    XftDrawDestroy(xftDraw);

    // Copy offscreen pixmap to window (double-buffering swap)
    XCopyArea(dpy, drawable, state.win, gc, 0, 0, w, h, 0, 0);
    XSync(dpy, False);
}
